package org.vacoor.nothing.common.reflect;

import java.lang.reflect.*;
import java.util.Arrays;

/**
 * This class instances our own
 * <code>ParameterizedTypes</code> , <code>GenericArrayTypes</code>.
 * These are not supposed to be mixed with Java's implementations - beware of
 * equality/identity problems.
 *
 * @author vacoor
 */
abstract class TypeFactory {

    /**
     * @param rawType
     * @param substTypeArgs
     * @param ownerType
     * @return
     */
    static ParameterizedType createParameterizedType(final Type rawType, final Type[] substTypeArgs, final Type ownerType) {
        return new ParameterizedType() {

            @Override
            public Type[] getActualTypeArguments() {
                return substTypeArgs;
            }

            @Override
            public Type getRawType() {
                return rawType;
            }

            @Override
            public Type getOwnerType() {
                return ownerType;
            }

            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof ParameterizedType)) {
                    return false;
                }
                ParameterizedType other = (ParameterizedType) obj;
                return Arrays.equals(getActualTypeArguments(), other.getActualTypeArguments())
                        && safeEquals(getRawType(), other.getRawType()) && safeEquals(
                        getOwnerType(), other.getOwnerType()
                );
            }

            @Override
            public int hashCode() {
                return safeHashCode(getActualTypeArguments()) ^ safeHashCode(getRawType()) ^ safeHashCode(getOwnerType());
            }

            @Override
            public String toString() {
                StringBuilder buff = new StringBuilder();
                if (ownerType != null) {
                    if ((ownerType instanceof Class)) {
                        buff.append(((Class) ownerType).getName());
                    } else {
                        buff.append(ownerType);
                    }
                    buff.append(".");
                    if ((ownerType instanceof ParameterizedType)) {
                        buff.append(rawType.toString().replace(ownerType.toString() + "$", ""));
                    } else {
                        buff.append(rawType);
                    }
                } else {
                    buff.append(rawType);
                }
                if ((substTypeArgs != null) && (substTypeArgs.length > 0)) {
                    buff.append("<");
                    int i = 1;
                    for (Type type : substTypeArgs) {
                        if (i == 0) {
                            buff.append(", ");
                        }
                        if ((type instanceof Class)) {
                            buff.append(((Class) type).getName());
                        } else {
                            buff.append(type.toString());
                        }
                        i = 0;
                    }
                    buff.append(">");
                }
                return buff.toString();
            }
        };
    }

    static Type createArrayType(Type componentType) {
        if (componentType instanceof Class<?>) {
            return Array.newInstance((Class<?>) componentType, 0).getClass();
        }
        return createGenericArrayType(componentType);
    }

    static GenericArrayType createGenericArrayType(final Type componentType) {
        return new GenericArrayType() {

            @Override
            public Type getGenericComponentType() {
                return componentType;
            }

            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof GenericArrayType)) {
                    return false;
                }
                GenericArrayType other = (GenericArrayType) obj;
                return safeEquals(getGenericComponentType(), other.getGenericComponentType());
            }

            @Override
            public int hashCode() {
                return safeHashCode(getGenericComponentType());
            }

            @Override
            public String toString() {
                return "[" + getGenericComponentType() + "]";
            }
        };
    }

    static WildcardType createWildcardType(final Type[] upperBounds, final Type[] lowerBounds) {
        return new WildcardType() {
            @Override
            public Type[] getUpperBounds() {
                return upperBounds;
            }

            @Override
            public Type[] getLowerBounds() {
                return lowerBounds;
            }

            @Override
            public boolean equals(Object other) {
                if (!(other instanceof WildcardType)) {
                    return false;
                }
                WildcardType wtype = (WildcardType) other;
                return Arrays.equals(upperBounds, wtype.getUpperBounds())
                        && Arrays.equals(lowerBounds, wtype.getLowerBounds());
            }

            @Override
            public int hashCode() {
                return safeHashCode(upperBounds) ^ safeHashCode(lowerBounds);
            }
        };
    }

    static <D extends GenericDeclaration> TypeVariable<D> createTypeVariable(final String name, final D genericDeclaration, final Type[] bounds) {
        return new TypeVariable<D>() {
            @Override
            public Type[] getBounds() {
                return bounds;
            }

            @Override
            public D getGenericDeclaration() {
                return genericDeclaration;
            }

            @Override
            public String getName() {
                return name;
            }

            @Override
            public boolean equals(Object o) {
                if (!(o instanceof TypeVariable<?>)) {
                    return false;
                }
                TypeVariable<?> other = (TypeVariable<?>) o;
                return Arrays.equals(bounds, other.getBounds())
                        && safeEquals(genericDeclaration, other.getGenericDeclaration())
                        && safeEquals(name, other.getName());
            }

            @Override
            public int hashCode() {
                return safeHashCode(bounds) ^ safeHashCode(genericDeclaration) ^ safeHashCode(name);
            }
        };
    }

    private static boolean safeEquals(Object t1, Object t2) {
        if (t1 == null) {
            return t2 == null;
        }
        return t1.equals(t2);
    }

    private static int safeHashCode(Object o) {
        if (o == null) {
            return 1;
        }
        return o.hashCode();
    }

    private TypeFactory() {
    }
}